﻿Imports Newtonsoft.Json
Imports Newtonsoft.Json.JsonConvert
Imports System.Windows.Forms

Public Class DataBase

    Private _fileName As String
    Private _dataBase As DataFile
    Public Shadows Event LoadCompleted As EventHandler(Of CompleteEventArgs)
    Public Shadows Event SaveCompleted As EventHandler(Of CompleteEventArgs)

    Public Sub New()
        _dataBase = New DataFile()
    End Sub
    Public Sub New(filename As String, Optional onLoadCompleted As EventHandler(Of CompleteEventArgs) = Nothing)
        _dataBase = New DataFile()
        _fileName = filename
        Me.Read(onLoadCompleted)
    End Sub

    Public Function Read(Optional onLoadCompleted As EventHandler(Of CompleteEventArgs) = Nothing) As DataFile
        Return Me.Read(Me.FileName, onLoadCompleted)
    End Function
    Public Function Read(filename As String, Optional onLoadCompleted As EventHandler(Of CompleteEventArgs) = Nothing) As DataFile
        Dim state As Boolean = True
        Dim ex As New Exception("")

        If IO.File.Exists(filename) Then
            Dim reader As IO.StreamReader = Nothing
            Try
                reader = New IO.StreamReader(filename, Text.Encoding.UTF8, True)
                _dataBase = DeserializeObject(Of DataFile)(reader.ReadToEnd())
            Catch ex : state = False
            Finally
                If reader IsNot Nothing Then reader.Close()
            End Try
        End If

        Try
            If _dataBase.Version = 0 Then _dataBase.Version = 1
            If Trim(_dataBase.Author) = "" Then _dataBase.Author = My.User.Name
            If Trim(_dataBase.Create) = "" Then _dataBase.Create = Format(Now, "yyyy-MM-dd HH:mm:ss")
            If Trim(_dataBase.Update) = "" Then _dataBase.Update = Format(Now, "yyyy-MM-dd HH:mm:ss")
        Catch : End Try

        If onLoadCompleted IsNot Nothing Then AddHandler Me.LoadCompleted, onLoadCompleted
        RaiseEvent LoadCompleted(Me, New CompleteEventArgs(state, If(state, "", ex.Message)))
        If onLoadCompleted IsNot Nothing Then RemoveHandler Me.LoadCompleted, onLoadCompleted

        Return _dataBase
    End Function
    Public Function Save(Optional onSaveCompleted As EventHandler(Of CompleteEventArgs) = Nothing) As Boolean
        Return Me.SaveAs(Me.FileName, onSaveCompleted)
    End Function
    Public Function SaveAs(fileName As String, Optional onSaveCompleted As EventHandler(Of CompleteEventArgs) = Nothing) As Boolean
        Dim state As Boolean = True
        Dim ex As New Exception("")
        Dim writer As IO.StreamWriter = Nothing
        Try
            Dim content As String = SerializeObject(_dataBase, Formatting.None)
            writer = New IO.StreamWriter(fileName, False, Text.Encoding.UTF8)
            writer.Write(content)
        Catch ex : state = False
        Finally
            If writer IsNot Nothing Then writer.Close()
            If onSaveCompleted IsNot Nothing Then AddHandler Me.SaveCompleted, onSaveCompleted
            RaiseEvent SaveCompleted(Me, New CompleteEventArgs(state, If(state, "", ex.Message)))
            If onSaveCompleted IsNot Nothing Then RemoveHandler Me.SaveCompleted, onSaveCompleted
        End Try

        Return state
    End Function

    Public Function Add(database As DataFile, Optional onSaveCompleted As EventHandler(Of CompleteEventArgs) = Nothing) As Boolean
        For Each item As KeyValuePair(Of String, List(Of DataValue)) In database.Items
            For Each value In item.Value
                If Trim(item.Key) <> "" AndAlso Trim(value.Value) <> "" AndAlso item.Key <> value.Value Then
                    Me.Add(item.Key, value.Value, GetTags(database.Tags, value.Tags), False, onSaveCompleted)
                End If
            Next
        Next

        Return Me.Save(onSaveCompleted)
    End Function
    Public Function Add(fileSystem As FileSystem, tags() As String, Optional onSaveCompleted As EventHandler(Of CompleteEventArgs) = Nothing) As Boolean
        For Each row In fileSystem.GetValid
            With fileSystem.Work(row.Key)
                If Trim(.Source) <> "" AndAlso Trim(.Target) <> "" AndAlso .Source <> .Target Then
                    Me.Add(.Source, .Target, tags.ToArray, False, onSaveCompleted)
                End If
            End With
        Next

        Return Me.Save(onSaveCompleted)
    End Function
    Public Function Add(key As String, value As String, tags() As String, save As Boolean, Optional onSaveCompleted As EventHandler(Of CompleteEventArgs) = Nothing) As Boolean
        Dim tagList = GetTags(tags)
        Dim list As New List(Of DataValue)
        If Me.Data.ContainsKey(key) Then
            list = Me.Data(key)
            Dim index As Integer = list.FindIndex(Function(data) data.Value = value)
            If index < 0 Then
                list.Add(New DataValue(value, 1, tagList))
            Else
                For Each tag In tagList
                    If Not list(index).Tags.Contains(tag) Then
                        list(index).Tags.Add(tag)
                    End If
                Next
            End If
            Me.Data(key) = list
            Me.Update = Format(Now, "yyyy-MM-dd HH:mm:ss")
        Else
            list.Add(New DataValue(value, 1, tagList))
            Me.Data.Add(key, list)
            Me.Update = Format(Now, "yyyy-MM-dd HH:mm:ss")
        End If

        Return If(save, Me.Save(onSaveCompleted), True)
    End Function
    Public Function Edit(oldKey As String, oldValue As String, newKey As String, newValue As String, tags() As String, save As Boolean, Optional onSaveCompleted As EventHandler(Of CompleteEventArgs) = Nothing) As Boolean
        Call Remove(oldKey, oldValue, False)
        Return Add(newKey, newValue, tags, save, onSaveCompleted)
    End Function
    Public Function Remove(key As String, value As String, save As Boolean, Optional onSaveCompleted As EventHandler(Of CompleteEventArgs) = Nothing) As Boolean
        If Me.Data.ContainsKey(key) Then
            Dim list As List(Of DataValue) = Me.Data(key)
            Dim index As Integer = list.FindIndex(Function(match) match.Value = value)
            If index > -1 Then
                If list.Count = 1 Then
                    Me.Data.Remove(key)
                Else
                    list.RemoveAt(index)
                    Me.Data(key) = list
                End If
            End If
        End If

        Return If(save, Me.Save(onSaveCompleted), True)
    End Function
    Public Function GetContainsList(key As String) As List(Of KeyValuePair(Of String, DataValue))
        Dim list As New List(Of KeyValuePair(Of String, DataValue))

        For Each query In Me.Data.Where(Function(match) match.Key.Contains(key) OrElse match.Value.LongCount(Function(item) item.Value.Contains(key)) > 0)
            For Each subquery In query.Value
                list.Add(New KeyValuePair(Of String, DataValue)(query.Key, subquery))
            Next
        Next

        Return list
    End Function

    Public Function GetTags(tags As String()) As List(Of Integer)
        Dim tagList As New List(Of Integer)
        For i = 0 To UBound(tags)
            If Not Me.Tags.Contains(Trim(tags(i))) Then
                Me.Tags.Add(Trim(tags(i)))
            End If
            tagList.Add(Me.Tags.IndexOf(Trim(tags(i))))
        Next
        Return tagList
    End Function
    Public Function GetTags(tags As List(Of Integer)) As String()
        Return GetTags(Me.Tags, tags)
    End Function
    Public Shared Function GetTags(tagList As List(Of String), tags As List(Of Integer)) As String()
        Dim list As New List(Of String)
        For Each index In tags
            If index < tags.Count Then
                list.Add(tagList(index))
            End If
        Next

        Return list.ToArray
    End Function
    Public Shared Function GetItem(data As Dictionary(Of String, List(Of DataValue)), key As String) As String
        Return GetItem(data, key, False)
    End Function
    Public Shared Function GetItem(data As Dictionary(Of String, List(Of DataValue)), key As String, checkTags As Boolean, Optional mainTags As Integer() = Nothing, Optional secondTags As Integer() = Nothing) As String
        mainTags = If(mainTags, {})
        secondTags = If(secondTags, {})
        If (Not checkTags OrElse mainTags.Concat(secondTags).Count > 0) AndAlso data.ContainsKey(key) Then
            Dim list As List(Of DataValue) = data(key)
            If Not checkTags Then Return If(list.Count > 0, list.Max.Value, "")

            Dim temp As New List(Of DataValue)
            For Each item As DataValue In list
                If item.Tags.Exists(Function(match) mainTags.Contains(match)) Then Return item.Value
                If item.Tags.Exists(Function(match) secondTags.Contains(match)) Then temp.Add(item)
            Next
            Return If(temp.Count > 0, temp.Max.Value, "")
        Else
            Return ""
        End If
    End Function

    Public Function GetSimilarityList(key As String, Optional minSimilarityLong As Double = 85, Optional minSimilarityShort As Double = 70, Optional maxToleranceLong As Double = 20, Optional maxToleranceShort As Double = 10) As List(Of CompareResult)
        Return Module1.GetSimilarityList(key, Me.Data, minSimilarityLong, minSimilarityShort, maxToleranceLong, maxToleranceShort)
    End Function

    Public ReadOnly Property FileName As String
        Get
            Return _fileName
        End Get
    End Property

    Public Property Version As Integer
        Get
            Return _dataBase.Version
        End Get
        Set(value As Integer)
            _dataBase.Version = value
        End Set
    End Property
    Public Property Author As String
        Get
            Return _dataBase.Author
        End Get
        Set(value As String)
            _dataBase.Author = value
        End Set
    End Property
    Public Property Create As String
        Get
            Return _dataBase.Create
        End Get
        Set(value As String)
            _dataBase.Create = value
        End Set
    End Property
    Public Property Update As String
        Get
            Return _dataBase.Update
        End Get
        Set(value As String)
            _dataBase.Update = value
        End Set
    End Property
    Public Property Tags As List(Of String)
        Get
            Return _dataBase.Tags
        End Get
        Set(value As List(Of String))
            _dataBase.Tags = value
        End Set
    End Property

    Public ReadOnly Property Count As Integer
        Get
            Return Me.Data.Count
        End Get
    End Property
    Public ReadOnly Property ContainsKey(key As String) As Boolean
        Get
            Return Me.Data.ContainsKey(key)
        End Get
    End Property
    Default Public ReadOnly Property Item(key As String, checkTags As Boolean, Optional mainTags As Integer() = Nothing, Optional secondTags As Integer() = Nothing) As String
        Get
            Return GetItem(Me.Data, key, checkTags, mainTags, secondTags)
        End Get
    End Property

    Public ReadOnly Property Data() As Dictionary(Of String, List(Of DataValue))
        Get
            Return _dataBase.Items
        End Get
    End Property
    Public ReadOnly Property DataFile As DataFile
        Get
            Return _dataBase
        End Get
    End Property
End Class